var util = require("util");
var utils = require("../utils");
var noOp = utils.noOp;

String.prototype.toCamel = function(){
  var camel = this.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');});
  return camel.charAt(0).toLowerCase() + camel.slice(1);
 };

exports.init = function (models) {
  
  function getMetadata(modelName) {
    return function(keys, callback, retrieveDbObject) {
      var thisObject = this;
      if (!keys) {
        callback(null, {});
        return;
      }
      var query = {key: {"inq": keys.slice(0)}};
      if (modelName) {
        retrieveMetadata(query, retrieveDbObject, thisObject, callback, modelName);
      } else {
        retrieveMetadata(query, retrieveDbObject, thisObject, callback);
      }
    };
  }
  
  function getAllMetadata() {
    return function(system, callback, retrieveDbObject) {
      var query = {system: system};
      retrieveMetadata(query, retrieveDbObject, this, callback);
    };
  }
  
  function retrieveMetadata(query, retrieveDbObject, objPointer, callback, modelName) {
    var metadataField = modelName ? modelName.toCamel() + "Metadata" : "metadata";
    var result = {};
    objPointer[metadataField]({where: query}, function(err, metadata){
      for (var i = 0; i < metadata.length; i++) {
        var meta = metadata[i];
        meta.value = JSON.parse(meta.value || null);
        var metaVal = null;
        if (retrieveDbObject) {
          metaVal = meta;
        } else {
          metaVal = meta.value;
        }
        if (!result[meta.key]) {
          result[meta.key] = {};
        }
        if (modelName) {
          var map = result[meta.key];
          map[meta[modelName.toCamel() + "Pk"]] = metaVal;
          result[meta.key] = map;
        } else {
          result[meta.key] = metaVal;
        }
      }
      callback(err, result);
    });
  }

  function setMetadata(modelName, foreignKeys) {
    return function(data, callback) {
      if (!data) {
        callback(null, {});
        return;
      }
      var keys = Array();
      for (var i = 0; i < data.length; i++) {
        keys.push(data[i].key);
      }
      var result = {};
      var foreignKey = this.id;
      var foreignPKey = this.pk;
      var currentModel = this;

      this.getMetadata(keys, function(err, metadata) {
        for (var i = 0; i < data.length; i++) {
          var item = data[i];
          var meta = metadata[item.key];
          if (meta) {
            meta.value = item.value;
            meta.system = item.system || false;
          } else {
            var newMeta = {key: item.key, value: item.value, system: item.system || false};
            newMeta[modelName.toCamel() + "Id"] = foreignKey;
            newMeta[modelName.toCamel() + "Pk"] = foreignPKey;
            for (var fk = 0; fk < foreignKeys.length; fk++) {
              var foreignKeyName = foreignKeys[fk].toCamel();
              newMeta[foreignKeyName + "Id"] = currentModel[foreignKeyName + "Id"];
              newMeta[foreignKeyName + "Pk"] = currentModel[foreignKeyName + "Pk"];
            }
            meta = new models[modelName + "Metadata"](newMeta);
          }
          meta.save();
          meta.value = JSON.parse(meta.value || null);
          result[item.key] = meta;
        }
        callback(err, result);
      }, /* retrieveDbObject */ true);
    };
  };
  
  models.Player.prototype.getMetadata = getMetadata();
  models.Game.prototype.getMetadata = getMetadata();
  models.Game.prototype.getGamePlayerMetadata = getMetadata("GamePlayer");
  models.Game.prototype.getAllGamePlayerMetadata = getAllMetadata("GamePlayer");
  models.GamePlayer.prototype.getMetadata = getMetadata();
  models.Play.prototype.getMetadata = getMetadata();
  
  models.Player.prototype.getAllMetadata = getAllMetadata();
  models.Game.prototype.getAllMetadata = getAllMetadata();
  models.GamePlayer.prototype.getAllMetadata = getAllMetadata();
  models.Play.prototype.getAllMetadata = getAllMetadata();
  
  models.Player.prototype.setMetadata = setMetadata("Player", []);
  models.Game.prototype.setMetadata = setMetadata("Game", []);
  models.GamePlayer.prototype.setMetadata = setMetadata("GamePlayer", ["Game"]);
  models.Play.prototype.setMetadata = setMetadata("Play", []);
  
  models.Player.prototype.getAvatar = function(callback) {
    this.getMetadata(["remoteAvatar"], function (err, metadata) {
      callback(err, metadata.remoteAvatar);
    });
  };
  
  models.GamePlayer.prototype.displayFormat = function(callback) {
    var gamePlayer = {
      pk          : this.pk,
      name        : this.name,
      online      : this.online,
      status      : this.status,
      creationTime: this.creationTime
    };
    var thisGamePlayer = this;
    this.getAllMetadata(/* system */ false, function(err, metadata) {
      thisGamePlayer.player(function(err, player){
        for (key in metadata) {
          gamePlayer[key] = metadata[key];
        }
        player.displayFormat(function(err, p) {
          gamePlayer.player = p;
          thisGamePlayer.game(function(err, game) {
            gamePlayer.gamePk = game.pk;
            callback(err, gamePlayer);
          });
        });
      });
    });
  };
  
  models.Game.prototype.displayFormat = function(callback) {
    var game = {
      pk: this.pk,
      session: this.session,
      status: this.status,
      gamePlayers: new Array(),
      plays: new Array(),
      creationTime: this.creationTime,
    };
    
    var thisGame = this;
    var lastPlayTime = null;
    
    // Add creatorPlayer
    this.creatorPlayer(function(err, creatorPlayer) {
      creatorPlayer.displayFormat(function(err, creatorPlayer) {
        game.creatorPlayer = creatorPlayer;
        // Add metadata
        thisGame.getAllMetadata(/* system */ false, function(err, metadata) {
          for(key in metadata) {
            game[key] = metadata[key];
          }
          var gamePlayerCounter = 0;
          // Add gamePlayers
          thisGame.gamePlayers(function(err, gamePlayers) {
            for (var i = 0; i < gamePlayers.length; i++) {
              gamePlayers[i].displayFormat(function(err, gamePlayer) {
                game.gamePlayers.push(gamePlayer);
                if (++gamePlayerCounter == gamePlayers.length) {
                  thisGame.plays({where: {session: thisGame.session}}, function(err, plays) {
                    if (plays.length == 0) {
                      callback(null, game);
                      return;
                    }
                    var playCounter = 0;
                    // Add plays
                    for (var j = 0; j < plays.length; j++) {
                      if (!lastPlayTime || plays[j].creationTime > lastPlayTime) {
                        lastPlayTime = plays[j].creationTime;
                      }
                      plays[j].displayFormat(function(err, play) {
                        game.plays.push(play);
                        if (++playCounter == plays.length) {
                          game.lastPlayTime = lastPlayTime;
                          callback(null, game);
                        }
                      });
                    }
                  });
                }
              });
            }
          });
        });
      });
    });
  };
  
  models.Player.prototype.displayFormat = function(callback) {
    var player = {
      pk          : this.pk,
      name        : this.name,
      online      : this.online,
      creationTime: this.creationTime,
    };
    this.getAllMetadata(/* system */ false, function(err, metadata) {
      for (key in metadata) {
        player[key] = metadata[key];
      }
      callback(err, player);
    });
  };
  
  models.Play.prototype.displayFormat = function(callback) {
    var play = {
      pk          : this.pk,
      name        : this.name,
      type        : this.type,
      creationTime: this.creationTime,
      session     : this.session
    };
    var thisPlay = this;
    this.getAllMetadata(/* system */ false, function(err, metadata) {
      for (key in metadata) {
        play[key] = metadata[key];
      }
      thisPlay.gamePlayer(function(err, gamePlayer) {
        play.gamePlayerPk = gamePlayer.pk;
        callback(err, play);
      });
    });
  };
  
  models.Game.prototype.activeGamePlayers = function(callback) {
    this.gamePlayers({where: {status: "active"}}, function(err, gamePlayers) {
      callback(err, gamePlayers);
    });
  };
};